﻿using System.Collections.Generic;
using System.IO;
using System.Net.Sockets;
using Nutilities.Threading;

namespace Nutilities.Net.Binary
{
    public abstract class ThreadedConnection<TCommand> : ThreadClass, IThreadedConnection
    {
        public delegate void CommandHandler(BinaryReader reader);
        public delegate void ThreadedConnectionEvent(ThreadedConnection<TCommand> connection);

        public event ThreadedConnectionEvent Disconnected; 

        public Socket Socket { get; private set; }

        public BinaryReader Reader { get; private set; }
        public BinaryWriter Writer { get; private set; }

        public Dictionary<TCommand, CommandHandler> CommandHandlers { get; private set; }

        protected ThreadedConnection(Socket socket)
        {
            ThreadName = "ThreadedConnection";

            Socket = socket;
            
            NetworkStream stream = new NetworkStream(socket);
            Reader = new BinaryReader(stream);
            Writer = new BinaryWriter(stream);

            CommandHandlers = new Dictionary<TCommand, CommandHandler>();
        }

        protected ThreadedConnection(ThreadedConnection<TCommand> connection)
        {
            ThreadName = "ThreadedConnection";

            Socket = connection.Socket;
            Reader = connection.Reader;
            Writer = connection.Writer;
        }

        public override void Stop()
        {
            base.Stop();
            Close();
        }

        public virtual void Close()
        {
            Reader.Close();
            Writer.Close();
            Socket.Close();
        }

        protected override void Run()
        {
            while (Running)
            {
                HandlePausedState();

                try
                {
                    TCommand command = ReadCommand();
                    OnCommandReceived(command);
                }
                catch (IOException)
                {
                    if (!Socket.Connected)
                    {
                        Stop();
                        OnDisconnected();
                    }
                }
            }
        }

        protected virtual void OnCommandReceived(TCommand command)
        {
            TriggerCommandHandler(command);
        }

        protected virtual void TriggerCommandHandler(TCommand message)
        {
            if (CommandHandlers.ContainsKey(message))
            {
                CommandHandlers[message](Reader);
            }
        }

        public virtual void OnDisconnected()
        {
            if (Disconnected != null)
                Disconnected(this);
        }

        protected abstract TCommand ReadCommand();
    }
}
